ここでは Flex の使用例をさらにいくつか紹介します。ここでの例は、必ずしも最適な実装ではありませんが、一般的な Flex の使い方を示してくれるはずです。
以下の定義は、与えられたファイルの中の単語数、文字数、行数をカウントするのに Flex を使う方法を示す簡単な例です。実際に Flex に関係のある部分は非常に少ないことに注意してください。以下のコードのほとんどは、コマンドライン・パラメータを処理したりカウントの合計を保持したりするものです。
/*
* ルールはここから始まる
*/
%%
[\n] { numchars++;
numlines++; }
[^ \t\n]+ { numwords++; numchars += yyleng;
}
.
{ numchars++; }
%%
/*
* 追加的な C コードがここから始まる。ここで、すべての引数処理等を行うコードが提供される。
*/
void main(int argc, char **argv)
{
int loop;
int lflag = 0; /* 行数をカウントする場合は 1 */
int wflag = 0; /* 単語数をカウントする場合は 1 */
int cflag = 0; /* 文字数をカウントする場合は 1 */
int fflag = 0; /* ファイル名が指定されている場合は
1 */
for(loop=1; loop<argc; loop++){
char *tmp = argv[loop];
if(tmp[0] == '-'){
switch(tmp[1]){
case 'l':
lflag = 1;
break;
case 'w':
wflag = 1;
break;
case 'c':
cflag = 1;
break;
default:
fprintf(stderr,"unknown
option -%c\n",tmp[1]);
}
} else {
fflag = 1;
numlines = numchars = numwords = 0;
if((yyin = fopen(tmp,"rb")) != 0){
(void) yylex();
fclose(yyin);
totwords += numwords;
totchars += numchars;
totlines += numlines;
printf("file : %25s :",tmp)
;
if(lflag){
fprintf(stdout,"lines
%5d ",numlines);
}
if(cflag){
fprintf(stdout,"characters
%5d ",numchars);
}
if(wflag){
fprintf(stdout,"words
%5d ",numwords);
}
fprintf(stdout,"\n");
}else{
fprintf(stderr,"wc : file
not found %s\n",tmp);
}
}
}
if(!fflag){
fprintf(stderr,"usage : wc [-l -w -c] file [file...]\n");
fprintf(stderr,"-l = count lines\n");
fprintf(stderr,"-c = count characters\n");
fprintf(stderr,"-w = count words\n");
exit(1);
}
for(loop=0;loop<79;loop++){
fprintf(stdout,"-");
}
fprintf(stdout,"\n");
fprintf(stdout,"total : %25s ","");
if(lflag){
fprintf(stdout,"lines %5d ",totlines);
}
if(cflag){
fprintf(stdout,"characters %5d ",totchars);
}
if(wflag){
fprintf(stdout,"words %5d ",totwords);
}
fprintf(stdout,"\n"};
}
ここでは、Pascal のような言語用の字句スキャナを構築する方法を示します。このスキャナ定義では、個々のキーワードがルールとしてリストされています。(すべてのキーワードをテーブルに格納してからテーブル検索を使う手法が一般的にはよく見られますが、)ここでの方法はキーワードと識別子とを区別するための方法としては、一般的に最も簡単なものです。また、識別子用にただ1つのルールがあるという点に注意してください。多くの場合、これはシンボル・テーブルを管理するためのサブルーチンを呼び出します。
もう1つ注意すべきなのは、_FILE と _BEGIN が先頭にアンダースコア _ を持つという点です。Flex もしくは C で定義されているいくつかの名前は、追加的な工夫なしでは使えないということを示すためにこのようにしてあります。これよりももっと一般的に使われる手法は、すべてのトークンの先頭もしくは末尾に何らかの文字列を付加するというもので、こうすることによって問題は発生しなくなります。TOK や SYM が一般的によく使われる拡張子です。
%{
#include <stdio.h>
#include "y.tab.h"
int line_number = 0;
void yyerror(char *message);
%}
%x COMMENT1 COMMENT2
white_space
[ \t]*
digit
[0-9]
alpha
[A-Za-z_]
alpha_num
({alpha}|{digit})
hex_digit
[0-9A-F]
identifier
{alpha}{alpha_num}*
unsigned_integer {digit}+
hex_integer
${hex_digit}{hex_digit}*
exponent
e[+-]?{digit}+
i
{unsigned_integer}
real
({i}\.{i}?|{i}?\.{i}){exponent}?
string
\'([^'\n]|\'\')+\'
bad_string
\'([^'\n]|\'\')+
%%
"{"
BEGIN(COMMENT1);
<COMMENT1>[^}\n]+
<COMMENT1>\n
++line_number;
<COMMENT1><<EOF>> yyerror("EOF in
comment");
<COMMENT1>"}"
BEGIN(INITIAL);
"(*"
BEGIN(COMMENT2);
<COMMENT2>[^)*\n]+
<COMMENT2>\n
++line_number;
<COMMENT2><<EOF>> yyerror("EOF in
comment");
<COMMENT2>"*)"
BEGIN(INITIAL);
<COMMENT2>[*)]
/*
* FILE と BEGIN は Flex もしくは C において既に定義されているため使うことができない点に注意してください。これは、すべてのトークンの先頭に
TOK_ やその他の接頭辞を付加することによって、よりすっきりと克服することができます。
*/
and
return(AND);
array
return(ARRAY);
begin
return(_BEGIN);
case
return(CASE);
const
return(CONST);
div
return(DIV);
do
return(DO);
downto
return(DOWNTO);
else
return(ELSE);
end
return(END);
file
return(_FILE);
for
return(FOR);
function
return(FUNCTION);
goto
return(GOTO);
if
return(IF);
in
return(IN);
label
return(LABEL);
mod
return(MOD);
nil
return(NIL);
not
return(NOT);
of
return(OF);
packed
return(PACKED);
procedure
return(PROCEDURE);
program
return(PROGRAM);
record
return(RECORD);
repeat
return(REPEAT);
set
return(SET);
then
return(THEN);
to
return(TO);
type
return(TYPE);
until
return(UNTIL);
var
return(VAR);
while
return(WHILE);
with
return(WITH);
"<="|"=<"
return(LEQ);
"=>"|">="
return(GEQ);
"<>"
return(NEQ);
"="
return(EQ);
".." return(DOUBLEDOT);
{unsigned_integer}
return(UNSIGNED_INTEGER);
{real}
return(REAL);
{hex_integer}
return(HEX_INTEGER);
{string}
return(STRING);
{bad_string}
yyerror("Unterminated string");
{identifier} return(IDENTIFIER);
[*/+\-,^.;:()\[\]] return(yytext[0]);
{white_space}
/* 何もしない */
\n
line_number += 1;
.
yyerror("Illegal input");
%%
void yyerror(char *message)
{
fprintf(stderr,"Error: \"%s\" in line %d. Token = %s\n",
message,line_number,yytext);
exit(1);
}
ここでは、スタート状態を使って、Flex により生成されるスキャナの内部に小規模のパーサを構築する方法の例を示します。このコードは The New Hackers Dictionary( prep.ai.mit.edu およびその他の多くのインターネット FTP サイトから入手可能なテキスト形式のもの)を入力として受け取り、すぐに製版および印刷できる状態の Texinfo フォーマットのドキュメントに変換するものです。このコードは jargon2910.ascii を使ってテスト済みです。
典型的な使い方は以下のとおりです。
%{
#define MAX_STATES 1024
#define TRUE 1
#define FALSE 0
#define CHAPTER "@chapter"
#define SECTION "@section"
#define SSECTION "@subsection"
#define SSSECTION "@subsubsection"
int states[MAX_STATES];
int statep = 0;
int need_closing = FALSE;
char buffer[YY_BUF_SIZE];
extern char *yytext;
/*
* このプログラムが生成する *.texinfo ファイルの先頭部分を構築する
* これは標準的な Texinfo ヘッダである
*/
void print_header(void)
{
printf("\\input texinfo @c -*-texinfo-*-\n");
printf("@c %c**start of header\n",'%');
printf("@setfilename jargon.info\n");
printf("@settitle The New Hackers Dictionary\n");
printf("@synindex fn cp\n");
printf("@synindex vr cp\n");
printf("@c %c**end of header\n",'%');
printf("@setchapternewpage odd\n");
printf("@finalout\n");
printf("@c @smallbook\n");
printf("\n");
printf("@c ====================================================\n\n");
printf("@c This file was produced by j2t. Any mistakes
are *not*\n");
printf("@c the fault of the jargon file editors.\n");
printf("@c ====================================================\n\n");
printf("@titlepage\n");
printf("@title The New Hackers Dictionary\n");
printf("@subtitle Version 2.9.10\n");
printf("@subtitle Generated by j2t\n")
printf("@author Eric S. Raymond, Guy L. Steel, and Mark
Crispin\n");
printf("@end titlepage\n");
printf("@page\n");
printf("@c ====================================================\n");
printf("\n\n");
printf("@unnumbered Preface\n");
printf("@c *******\n");
}
/*
* 生成される Texinfo ファイルの末尾の部分を作成する
*/
void print_trailer(void)
{
printf("\n");
printf("@c ====================================================\n");
printf("@contents\n"); /* 目次を表示する */
printf("@bye\n\n");
}
/*
* 節もしくは章に、後にそれを見つけることができるよう、下線を引く
*/
void write_underline(int len, int space, char ch)
{
int loop;
printf("@c ");
for(loop=3; loop<space; loop++){
printf(" ");
}
while(len--){
printf("%c",ch);
}
printf("\n\n");
}
/*
* Texinfo において特殊な意味を持つ文字をチェックし、エスケープする
*/
char *check_and_convert(char *string)
{
int buffpos = 0;
int len,loop;
len = strlen(string);
for(loop=0; loop<len; loop++){
if(string[loop] == '@' ||
string[loop] == '{' ||
string[loop] == '}')
{
buffer[buffpos++] = '@';
buffer[buffpos++] = string[loop];
} else {
buffer[buffpos++] = string[loop];
}
}
buffer[buffpos] = '\0';
return(buffer);
}
/*
* 章、節、項のヘッダを書き出す
*/
void write_block_header(char *type)
{
int loop;
int len;
(void)check_and_convert(yytext);
len = strlen(buffer);
for(loop=0; buffer[loop] != '\n';loop++);
buffer[loop] = '\0';
printf("%s %s\n",type,buffer);
write_underline(strlen(buffer),strlen(type)+1,'*');
}
%}
/*
* Flex の記述情報がここから始まる
*/
%x HEADING EXAMPLE ENUM EXAMPLE2
%x BITEM BITEM_ITEM
%s LITEM LITEM2
%%
^#[^#]*"#" /* ヘッダとフッタをスキップする */
/*
* 章は、その下にアスタリスクを持ち、コロンで終わる
*/
^[^\n:]+\n[*]+\n
write_block_header(CHAPTER);
^"= "[A-Z]" ="\n"="*
{ /* 個々のカテゴリごとに節を作成する */
if(need_closing == TRUE){
printf("@end table\n\n\n");
}
need_closing == TRUE;
write_block_header(SECTION);
printf("\n\n@table @b\n");
}
"Examples:"[^\.]+ ECHO;
"*"[^*\n]+"*"
{ /* @emph{}(強調された)テキスト */
yytext[yyleng-1] = '\0';
(void)check_and_convert(&yytext[1]);
printf("@i{%s}",buffer);
}
"{{"[^}]+"}}"
{ /* 特別な強調 */
yytext[yyleng-2] = '\0';
(void)check_and_convert(&yytext[2]);
printf("@strong{%s}",buffer);
}
"{"[^}]+"}"
{ /* 特別な強調 */
yytext[yyleng-1] = '\0';
(void)check_and_convert(&yytext[1]);
printf("@b{%s}",buffer);
}
/* 特殊な Texinfo 文字をエスケープする */
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"@"
printf("@@");
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"{"
printf("@{");
<INITIAL,LITEM,LITEM2,BITEM,ENUM,EXAMPLE,EXAMPLE2>"}"
printf("@}");
/*
* @example コードを再生成する
*/
":"\n+[^\n0-9*]+\n" "[^ ] {
int loop;
int len;
int cnt;
printf(":\n\n@example \n");
strcpy(buffer,yytext);
len = strlen(buffer);
cnt = 0;
for(loop=len; loop > 0;loop--){
if(buffer[loop] == '\n')
cnt++;
if(cnt == 2)
break;
}
yyless(loop+1);
statep++;
states[statep] = EXAMPLE2;
BEGIN(EXAMPLE2);
}
<EXAMPLE,EXAMPLE2>^\n {
printf("@end example\n\n");
statep--;
BEGIN(states[statep]);
}
/*
* @enumerate リストを再生成する
*/
":"\n+[ \t]*[0-9]+"."
{
int loop;
int len;
printf(":\n\n@enumerate \n");
strcpy(buffer,yytext);
len = strlen(buffer);
for(loop=len, loop > 0;loop--){
if(buffer[loop] == '\n')
break;
}
yyless(loop);
statep++;
states[statep] = ENUM;
BEGIN(ENUM);
}
<ENUM>"@"
printf("@@");
<ENUM>":"\n+" "[^0-9]
{
printf(":\n\n@example\n");
statep++;
states[statep] = EXAMPLE;
BEGIN(EXAMPLE);
}
<ENUM>\n[ \t]+[0-9]+"."
{
printf("\n\n@item ");
}
<ENUM>^[^ ] |
<ENUM>\n\n\n[ \t]+[^0-9] {
printf("\n\n@end enumerate\n\n");
statep--;
BEGIN(states[statep]);
}
/*
* 1種類の @itemize リストを再生成する
*/
":"\n+":"
{
int loop;
int len;
printf(":\n\n@itemize @bullet \n");
yyless(2);
statep++;
states[statep] = LITEM2;
BEGIN(LITEM2);
}
<LITEM2>^":".+":"
{
(void)check_and_convert(&yytext[1]);
buffer[strlen(buffer)-1]='\0';
printf("@item @b{%s:}\n",buffer);
}
<LITEM2>\n\n\n+[^:\n]
{
printf("\n\n@end itemize\n\n");
ECHO;
statep--;
BEGIN(states[statep]);
}
/*
* リビジョン・ヒストリ部からリストを作成する。ここで Version
が必要なのは、そうしないと他のルールと衝突するからである
*/
:[\n]+"Version"[^:\n*]+":"
{
int loop;
int len;
printf(":\n\n@itemize @bullet \n");
strcpy(buffer,yytext);
len = strlen(buufer);
for(loop=len; loop > 0;loop--){
if(buffer[loop] == '\n')
break;
}
yyless(loop);
statep++;
states[statep] = LITEM;
BEGIN(LITEM);
}
<LITEM>^.+":"
{
(void)check_and_convert(yytext);
buffer[strlen(buffer)-1]='\0';
printf("@item @b{%s}\n\n",buffer);
}
<LITEM>^[^:\n]+\n\n[^:\n]+\n {
int loop;
strcpy(buffer,yytext);
for(loop=0; buffer[loop] != '\n'; loop++);
buffer[loop] = '\0';
printf("%s\n",buffer);
printf("@end itemize\n\n");
printf("%s",&buffer[loop+1]);
statep--;
BEGIN(states[statep]);
}
/*
* @itemize @bullet リストを再生成する
*/
":"\n[ ]*"*"
{
int loop;
int len;
printf(":\n\n@itemize @bullet \n");
len = strlen(buffer);
for(loop=0; loop < len;loop++){
if(buffer[loop] == '\n')
break;
}
yyless((len-loop)+2);
statep++;
states[statep] = BITEM;
BEGIN(BITEM);
}
<BITEM>^" "*"*"
{
printf("@item");
statep++;
states[statep] = BITEM_ITEM;
BEGIN(BITEM_ITEM);
}
<BITEM>"@"
printf("@@");
<BITEM>^\n
{
printf("@end itemize\n\n");
statep--;
BEGIN(states[statep]);
}
<BITEM_ITEM>[^\:]*
{
printf(" @b{%s}\n\n",check_and_convert(yytext));
}
<BITEM_ITEM>":"
{
statep--;
BEGIN(states[statep]);
}
/*
* @chapter、@section 等を再作成する
*/
^:[^:]*
{
(void)check_and_convert(&yytext[1]);
statep++;
states[statep] = HEADING;
BEGIN(HEADING);
}
<HEADING>:[^\n]
{
printf("@item @b{%s}\n",buffer);
write_underline(strlen(buffer),6,'~');
statep--;
BEGIN(states[statep]);
}
<HEADING>:\n"*"*
{
if(need_closing == TRUE){
printf("@end table\n\n\n");
need_closing = FALSE;
}
printf("@chapter %s\n",buffer);
write_underline(strlen(buffer),9,'*');
statep--;
BEGIN(states[statep]);
}
<HEADING>:\n"="*
{
if(need_closing == TRUE){
printf("@end table\n\n\n");
need_closing = FALSE;
}
printf("@section %s\n",buffer);
write_underline(strlen(buffer),9,'=');
statep--;
BEGIN(states[statep]);
}
<HEADING>"@"
printf("@@");
<HEADING>:\n"-"*
{
if(need_closing == TRUE){
printf("@end table\n\n\n");
need_closing = FALSE;
}
printf("@subsection %s\n",buffer);
write_underline(strlen(buffer),12,'-');
statep--;
BEGIN(states[statep]);
}
/*
* @example テキストを再作成する
*/
^" "
{
printf("@example\n");
statep++;
states[statep] = EXAMPLE;
BEGIN(EXAMPLE);
}
<EXAMPLE>^" "
.
ECHO;
%%
/*
* 初期化して実行する
*/
int main(int argc, char *argv[])
{
states[0] = INITIAL;
statep = 0;
print_header();
yylex();
print_trailer();
return(0);
}
章、節、項 | これらの先頭にはいずれも同じパターンが来ます。
|
強調 | これは少し難しいのですが、一般的には強調は(イタリックの場合は) *...*、(強調文字(strong)の場合は){{...}}、(太字(bold)の場合は){...}
の対によって示されます。ここでは、これらを検索して、コマンドを出力します。
|
例および列挙されたリスト | これらはともにコロンで始まり、そのうしろに、1つ以上の改行、少なくとも5つの空白、そして最後に数字もしくは何らかのテキストが続きます。例えば、
...列挙: 0.何らかのテキスト
...例: (インデントされた)何らかのテキストが
|
項目化されマークを付けられたリスト | これらは、例および列挙されたリストによく似ていますが、違いは、項目の先頭にコロンもしくはアスタリスクがあり、末尾にコロンがあるという点です。
|
Copyright (C) 1992, 1993 Free Software Foundation
Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.
Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.
Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation.
日本語訳:市川和久
Japanese translation by Kazuhisa Ichikawa (ki@home.email.ne.jp)